namespace Microsoft.ServiceModel.Samples.Discovery
{
    using System;
    using System.Xml;
    using System.Xml.Serialization;
    using System.ServiceModel;
    using System.ServiceModel.Channels;
    using System.Diagnostics;
    using Microsoft.ServiceModel.Samples.Discovery.Protocol;

    [ServiceBehavior(
        InstanceContextMode = InstanceContextMode.Single,
        ConcurrencyMode = ConcurrencyMode.Multiple)]
    public sealed class AnnouncementListener : IAnnouncementContract, IDisposable
    {
        public event EventHandler<AnnouncementEventArgs> Hello;
        public event EventHandler<AnnouncementEventArgs> Bye;

        InputBinding inputBinding;
        ServiceHost serviceHost;
        bool disposed;

        public AnnouncementListener()
            : this(ProtocolUtility.CreateMulticastInputBinding())
        {
        }

        public AnnouncementListener(InputBinding inputBinding)
        {
            Initialize(inputBinding);
        }

        void Initialize(InputBinding inputBinding)
        {
            if (inputBinding == null)
                throw new ArgumentNullException("inputBinding");

            this.inputBinding = inputBinding;

            // create the host for this announcement listener service
            this.serviceHost = new ServiceHost(this);
            
            // add the endpoint to listen on the specified "ToURI" and 
            // "ListenURI" using the specified "Binding"
            this.serviceHost.AddServiceEndpoint(typeof(IAnnouncementContract),
                this.inputBinding.Binding,
                this.inputBinding.ToUri,
                this.inputBinding.ListenUri);

            this.disposed = false;
          
            // open the host and start listening for announcements
            this.serviceHost.Open();
        }

        #region AnnouncementContract Members

        void IAnnouncementContract.HelloOperation(HelloMessage message)
        {
            if (!ValidateMessage(message)) return;
            Debug.WriteLine("Received hello message");
            Debug.WriteLine(DebugUtility.ToString(message.Hello));

            if (this.Hello != null)
                this.Hello(this, new AnnouncementEventArgs(
                    message.Hello, message.AppSequence));
        }

        void IAnnouncementContract.ByeOperation(ByeMessage message)
        {
            if (!ValidateMessage(message)) return;
            Debug.WriteLine("Received bye message");
            Debug.WriteLine(DebugUtility.ToString(message.Bye));

            if (this.Bye != null)
                this.Bye(this, new AnnouncementEventArgs(
                    message.Bye, message.AppSequence));
        }

        #endregion

        static bool ValidateMessage(HelloMessage message)
        {
            if (!ValidateHeaders()) return false;

            // validate the content and drop invalid message
            if ((message.Hello == null) && (!message.Hello.IsValid()))
            {
                return false;
            }

            return true;
        }

        static bool ValidateMessage(ByeMessage message)
        {
            if (!ValidateHeaders()) return false;

            // validate the content and drop invalid message
            if ((message.Bye == null) && (!message.Bye.IsValid()))
            {
                return false;
            }

            return true;
        }

        static bool ValidateHeaders()
        {
            UniqueId msgId = OperationContext.Current.IncomingMessageHeaders.MessageId;
            if (msgId == null)
            {
                // drop the message because the required header MessageId is not present
                return false;
            }

            return true;
        }

        #region IDisposable Members

        public void Dispose()
        {
            if (!this.disposed)
            {
                if (this.serviceHost != null)
                {
                    this.serviceHost.Close();
                }

                this.disposed = true;
            }
        }

        #endregion
    }
}
